package com.sunlands.qdcy.tongzhuo.service;

import com.google.common.collect.Lists;
import com.sunlands.qdcy.tongzhuo.condition.UserDiaryCondition;
import com.sunlands.qdcy.tongzhuo.constant.RedisKeyConst;
import com.sunlands.qdcy.tongzhuo.constant.YN;
import com.sunlands.qdcy.tongzhuo.feign.UserCenterService;
import com.sunlands.qdcy.tongzhuo.feign.vo.UserInfoDTO;
import com.sunlands.qdcy.tongzhuo.model.DiaryDO;
import com.sunlands.qdcy.tongzhuo.model.UserDiaryReplyDO;
import com.sunlands.qdcy.tongzhuo.model.UserDiaryZanDO;
import com.sunlands.qdcy.tongzhuo.repository.DiaryRepository;
import com.sunlands.qdcy.tongzhuo.vo.DiaryVO;
import com.sunlands.qdcy.tongzhuo.vo.Page;
import com.sunlands.qdcy.tongzhuo.vo.UserDiaryCommentVO;
import com.sunlands.qdcy.tongzhuo.web.BaseController;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import static com.sunlands.qdcy.tongzhuo.config.RedisConfig.CacheKeys.DIARY_BY_ID;
import static com.sunlands.qdcy.tongzhuo.config.RedisConfig.CacheKeys.DIARY_DETAIL_BY_ID;
import static com.sunlands.qdcy.tongzhuo.model.DiaryDO.*;
import static com.sunlands.qdcy.tongzhuo.vo.DiaryVO.TypeEnums.diary;
import static java.util.regex.Pattern.compile;

/**
 * @author zhangxiaobin
 * @date 2019/5/2914:35
 */
@Slf4j
@Service
public class DiaryService extends BaseService {

    /**
     * 保存和修改都使用此方法
     *
     * @param diaryDO diaryDO
     * @return DiaryDO
     */
    @Caching(evict = {
            @CacheEvict(cacheNames = DIARY_BY_ID, key = "#diaryDO.id", condition = "#diaryDO.id != null")
    })
    public DiaryDO save(DiaryDO diaryDO) {
        diaryDO = diaryRepository.save(diaryDO);

        //发送消息
        if (!diaryDO.getCreateType().equals(DiaryDO.CreateTypeEnums.DELETE.type)) {
            diaryMessageService.diaryPublishNotice(diaryDO.getUserId(), diaryDO.getId());
        }
        return diaryDO;
    }

    /**
     * 根据id查询日记基本信息
     *
     * @param id id
     * @return DiaryDO
     */
    @Cacheable(cacheNames = DIARY_BY_ID, key = "#id")
    public DiaryDO findById(Long id) {
        return diaryRepository.findOne(id);
    }

    /**
     * 根据id查询日记详细信息
     *
     * @param diaryDO diaryDO
     * @return DiaryVO
     */
    @Cacheable(cacheNames = DIARY_DETAIL_BY_ID, key = "#diaryDO.id")
    public DiaryVO findDetail(DiaryDO diaryDO) {
        UserInfoDTO user = userCenterService.getUserInfoById(diaryDO.getUserId()).getData();
        return perfectDiary(diaryDO, user);
    }

    public static final int DIARY_FILTER_SIZE = 140;

    private DiaryVO perfectDiary(DiaryDO diaryDO, UserInfoDTO user) {
        Integer commentCountByDiaryId = userDiaryReplyService.getCommentCountByDiaryId(diaryDO.getId());
        Integer zanCountByDiaryId = userDiaryZanService.getZanCountByDiaryId(diaryDO.getId(), UserDiaryZanDO.TypeEnums.DIARY.code);


        DiaryVO diaryVO = DiaryVO.builder().content(verifiable(diaryDO.getContent(), DIARY_FILTER_SIZE))
                .createTime(diaryDO.getCreateTime())
                .id(diaryDO.getId())
                .imgUrls(Objects.isNull(diaryDO.getImgUrls()) ? null : Arrays.asList(diaryDO.getImgUrls().split(",")))
                .topicId(diaryDO.getTopicId())
                .topicName(diaryDO.getTopicName())
                .userAvatarUrl(user.getAvatarUrl())
                .createType(diaryDO.getCreateType())
                .userId(diaryDO.getUserId())
                .userCurrentLearnTargetName(user.getCurrentLearnTargetName())
                .userName(user.getName())
                .sex(user.getSex())
                .identityName(user.getIdentityName())
                .forwardId(diaryDO.getForwardId())
                .rootId(diaryDO.getRootId())
                .videoUrls(Objects.isNull(diaryDO.getVideoUrls()) ? null : Arrays.asList(diaryDO.getVideoUrls().split(",")))
                .commentCount(commentCountByDiaryId)
                .zanCount(zanCountByDiaryId)
                .seeByMyself(Objects.isNull(diaryDO.getSeeByMyself()) ? YN.NO.getCode() : diaryDO.getSeeByMyself())
                .forwardCount(forwardService.countDiaryForward(diaryDO.getId()))
                .type(diary.name())
                .build();

        filter(diaryVO);

        return diaryVO;
    }

    private void filter(DiaryVO diaryVO) {
        try {
            Long currentUserId = BaseController.currentUserId();
            if (Objects.nonNull(currentUserId)
                    && currentUserId.equals(394L)
                    && Objects.nonNull(diaryVO)
                    && !currentUserId.equals(diaryVO.getUserId())
                    && ("连麦计时".equals(diaryVO.getTopicName()) || "连麦学习".equals(diaryVO.getTopicName()))
                    && Objects.nonNull(diaryVO.getSex())
                    && diaryVO.getSex().equals(1)
            ) {
                diaryVO.setImgUrls(null);
            }
        }catch (Exception e){
            log.error("针对测试用户过滤日记出错！ diary = {}", diaryVO);
        }
    }

    public String verifiable(String content, int length) {
        try {
            if (length == 0) {
                StringBuilder temp = new StringBuilder(content);
                temp.insert(length, ' ');
                return temp.toString();
            }
            if (content.length() >= length) {
                String sign = content.substring(length, length + 1);
                if (isHasEmoji(sign)) {
                    length--;
                    if (isHasEmoji(content.substring(length, length + 1))) {
                        return verifiable(content, length);
                    }
                }
            }
            if (length < DIARY_FILTER_SIZE) {
                StringBuilder temp = new StringBuilder(content);
                temp.insert(length + 1, ' ');
                return temp.toString();
            }
        } catch (Exception e) {
            log.error("diary content verifiable error");
        }
        return content;
    }

    private boolean isHasEmoji(String reviewerName) {
        Pattern pattern = compile("[\ud000-\udfff]|[\u2600-\u27ff]");
        Matcher matcher = pattern.matcher(reviewerName);
        return matcher.find();
    }

    /**
     * @param topicId  话题id搜索条件
     * @param userId   用户id搜索条件
     * @param pageable 分页数据
     * @return PageResultVO
     */
    public Page<DiaryVO> page(Long topicId, Long userId, Pageable pageable, String createTime) {
        Specification<DiaryDO> specification = (Root<DiaryDO> root, CriteriaQuery<?> query, CriteriaBuilder cb) -> {
            List<Predicate> list = new ArrayList<>();
            //不查询删除的日记
            list.add(cb.notEqual(root.get(FILED_CREATE_TYPE), DiaryDO.CreateTypeEnums.DELETE.type));
            //userId不为空时，增加为搜索条件
            Optional.ofNullable(userId).ifPresent(uId -> list.add(cb.equal(root.get(FILED_USER_ID), uId)));
            //topicId不为空时，增加为搜索条件
            Optional.ofNullable(topicId).ifPresent(uId -> list.add(cb.equal(root.get(FILED_TOPIC_ID), uId)));


            if (org.apache.commons.lang.StringUtils.isNotEmpty(createTime)) {
                SimpleDateFormat dtf2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                try {
                    if (pageable.getPageNumber() == 0) {
                        list.add(cb.lessThan(root.get(FILED_CREATE_TIME), dtf2.parse(createTime)));
                    } else {
                        list.add(cb.lessThanOrEqualTo(root.get(FILED_CREATE_TIME), dtf2.parse(createTime)));
                    }
                } catch (ParseException e) {
                    e.printStackTrace();
                }
            }
            return cb.and(list.toArray(new Predicate[list.size()]));
        };
        org.springframework.data.domain.Page page = diaryRepository.findAll(specification, pageable);
        List<DiaryDO> content = page.getContent();
        List<Long> idList = content.stream().map(DiaryDO::getUserId).collect(Collectors.toList());
        List<UserInfoDTO> userList = userCenterService.list(idList).getData();
        List<DiaryVO> diaryVOS = content.stream().map(diary -> {
            for (UserInfoDTO user : userList) {
                if (diary.getUserId().equals(user.getId())) {
                    return perfectDiary(diary, user);
                }
            }
            return null;
        }).filter(Objects::nonNull).collect(Collectors.toList());
        return new Page<>(page.getNumber() + 1, page.getTotalElements(), diaryVOS);
    }

    /**
     * @param topicId  话题id搜索条件
     * @param userId   用户id搜索条件
     * @param pageable 分页数据
     * @return PageResultVO
     */
    public Page<DiaryVO> page(Long topicId, Long userId, Pageable pageable, Boolean self) {
        Specification<DiaryDO> specification = (root, query, cb) -> {
            List<Predicate> list = new ArrayList<>();
            //不查询删除的日记
            list.add(cb.notEqual(root.get(FILED_CREATE_TYPE), DiaryDO.CreateTypeEnums.DELETE.type));
            //userId不为空时，增加为搜索条件
            Optional.ofNullable(userId).ifPresent(uId -> list.add(cb.equal(root.get(FILED_USER_ID), uId)));
            //topicId不为空时，增加为搜索条件
            Optional.ofNullable(topicId).ifPresent(uId -> list.add(cb.equal(root.get(FILED_TOPIC_ID), uId)));

            if (Objects.nonNull(self) && !self)
                Optional.of(self).ifPresent(uId -> list.add(cb.notEqual(root.get(FILED_SEE_BY_MYSELF), 1)));

            return cb.and(list.toArray(new Predicate[0]));
        };
        org.springframework.data.domain.Page page = diaryRepository.findAll(specification, pageable);
        List<DiaryDO> content = page.getContent();
        List<Long> idList = content.stream().map(DiaryDO::getUserId).collect(Collectors.toList());
        List<UserInfoDTO> userList = userCenterService.list(idList).getData();
        List<DiaryVO> diaryVOS = content.stream().map(diary -> {
            for (UserInfoDTO user : userList) {
                if (diary.getUserId().equals(user.getId())) {
                    return perfectDiary(diary, user);
                }
            }
            return null;
        }).filter(Objects::nonNull).collect(Collectors.toList());
        return new Page<>(page.getNumber() + 1, page.getTotalElements(), diaryVOS);
    }


    public Page<DiaryVO> page(List<Long> userIdList, Pageable pageable) {
        Specification<DiaryDO> specification = (root, query, cb) -> {
            List<Predicate> list = new ArrayList<>();
            //不查询删除的日记
            list.add(cb.notEqual(root.get(FILED_CREATE_TYPE), DiaryDO.CreateTypeEnums.DELETE.type));
            //userId不为空时，增加为搜索条件
            if (Objects.nonNull(userIdList) && !userIdList.isEmpty()) {
                CriteriaBuilder.In<Object> in = cb.in(root.get(FILED_USER_ID));
                for (Long id : userIdList) {
                    in.value(id);
                }
                list.add(in);
            }
            return cb.and(list.toArray(new Predicate[list.size()]));
        };
        org.springframework.data.domain.Page page = diaryRepository.findAll(specification, pageable);
        List<DiaryDO> content = page.getContent();
        List<Long> idList = content.stream().map(DiaryDO::getUserId).collect(Collectors.toList());
        List<UserInfoDTO> userList = userCenterService.list(idList).getData();

        List<DiaryVO> diaryVOS = content.stream().map(diary -> {
            for (UserInfoDTO user : userList) {
                if (diary.getUserId().equals(user.getId())) {
                    return perfectDiary(diary, user);
                }
            }
            return null;
        }).filter(Objects::nonNull).collect(Collectors.toList());

        return new Page<>(page.getNumber() + 1, page.getTotalElements(), diaryVOS);
    }

    public List<DiaryVO> getByUserId(Long userId) {
        String userDiarylistCacheKey = RedisKeyConst.getUserDiarylist(userId);
        Object o = redisTemplate.opsForValue().get(userDiarylistCacheKey);
        if (o == null) {
            List<DiaryVO> diaryVos = new ArrayList<>();
            List<DiaryDO> userDiaryList = diaryRepository.findByUserId(userId);
            if (userDiaryList != null) {
                for (DiaryDO userDiary : userDiaryList) {
                    setDiaryVoInfos(diaryVos, userDiary);
                }
                redisTemplate.opsForValue().set(userDiarylistCacheKey, diaryVos, 300, TimeUnit.SECONDS);
            }
            return diaryVos;
        }
        return (List<DiaryVO>) o;
    }

    /**
     * 根据话题统计日记数量
     *
     * @param topicId topicId
     * @return Long
     */
    public Long countByTopicId(Long topicId) {
        return diaryRepository.countByTopicIdAndCreateTypeNot(topicId, DiaryDO.CreateTypeEnums.DELETE.type);
    }


    /**
     * 根据用户id统计日记数量
     *
     * @param userId userId
     * @return Long
     */
    public Long countByUserId(Long userId) {
        return diaryRepository.countByUserIdAndCreateTypeNot(userId, DiaryDO.CreateTypeEnums.DELETE.type);
    }


    /**
     * 分页日记列表
     *
     * @param condition
     * @return
     */
    public Page<DiaryVO> pageDiarys(UserDiaryCondition condition) {
        Page<DiaryVO> resultVO = new Page<>();
        List<String> orderKeys = new ArrayList<>();
        if (StringUtils.isNotEmpty(condition.getSortColumn())) {
            orderKeys.add(condition.getSortColumn());
        }
        Pageable pageable = null;
        if (StringUtils.isNotEmpty(condition.getSortType()) && StringUtils.isNotEmpty(condition.getSortColumn())) {
            Sort sort = new Sort(Sort.Direction.valueOf(condition.getSortType()), orderKeys);
            pageable = buildPageRequest(condition.getPageNumber(), condition.getPageSize(), sort);
        }
        org.springframework.data.domain.Page page = diaryRepository.findAll(new Specification<DiaryDO>() {
            @Override
            public Predicate toPredicate(Root<DiaryDO> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
                List<Predicate> predicates = new ArrayList<>();
                if (condition.getUserId() != null && 0L != condition.getUserId().longValue()) {
                    predicates.add(criteriaBuilder.equal(root.get("userId").as(Long.class), condition.getUserId()));
                }
                return criteriaBuilder.and(predicates.toArray(new Predicate[predicates.size()]));
            }
        }, pageable);
        List<DiaryVO> diaryVos = new ArrayList<>();
        long totalElements = page.getTotalElements();
        if (totalElements > 0) {
            List<DiaryDO> content = page.getContent();
            for (DiaryDO userDiary : content) {
                setDiaryVoInfos(diaryVos, userDiary);
            }
        }
        resultVO.setTotal(totalElements);
        resultVO.setRows(diaryVos);
        return resultVO;
    }

    private void deleteCache(Long diaryId, Long userId) {
        redisTemplate.delete(RedisKeyConst.getUserDiarylist(userId));
        redisTemplate.delete(RedisKeyConst.getUserDiaryOne(diaryId));
    }

    private void setDiaryVoInfos(List<DiaryVO> diaryVos, DiaryDO userDiary) {
        DiaryVO diaryVo = new DiaryVO();
        Long id = userDiary.getId();
        diaryVo.setId(id);
        diaryVo.setUserId(userDiary.getUserId());
        diaryVo.setContent(userDiary.getContent());
        //TODOdiaryVo.setImgUrls(userDiary.getImgUrls());
        diaryVo.setCreateTime(userDiary.getCreateTime());
        diaryVo.setCommentCount(userDiaryCommentService.getCommentCountByDiaryId(id));
        diaryVo.setZanCount(userDiaryZanService.getZanCountByDiaryId(id, UserDiaryZanDO.TypeEnums.DIARY.code));
        diaryVo.setForwardCount(forwardService.countDiaryForward(id));
        diaryVos.add(diaryVo);
    }

    /**
     * 评论列表页，不包含日记信息
     *
     * @param diaryId
     * @param page
     * @param size
     * @return
     */
    public Page<UserDiaryCommentVO> commentPageByDiaryId(Long diaryId, Long userId, Integer page, Integer size) throws InterruptedException, ExecutionException, TimeoutException {
        Specification<UserDiaryReplyDO> specification = new Specification<UserDiaryReplyDO>() {
            @Override
            public Predicate toPredicate(Root<UserDiaryReplyDO> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
                List<Predicate> predicateList = Lists.newArrayList();
                predicateList.add(criteriaBuilder.equal(root.get("diaryId").as(Long.class), diaryId));
                predicateList.add(criteriaBuilder.equal(root.get("type").as(Long.class), UserDiaryReplyDO.TypeEnums.DIARY_COMMENT.code));
                predicateList.add(criteriaBuilder.equal(root.get("status").as(Long.class), YN.YES.getCode()));
                return criteriaBuilder.and(predicateList.toArray(new Predicate[predicateList.size()]));
            }
        };
        Sort sort = new Sort(Sort.Direction.DESC, Lists.newArrayList("createTime"));
        PageRequest pageRequest = new PageRequest(page, size, sort).previous();
        org.springframework.data.domain.Page pageResult = userDiaryReplyService.findAll(specification, pageRequest);
        List<UserDiaryReplyDO> pageResultContent = pageResult.getContent();
        List<UserDiaryCommentVO> userDiaryCommentList = new ArrayList<>(size);
        if (null != pageResultContent && pageResultContent.size() > 0) {
            for (UserDiaryReplyDO diaryComment : pageResultContent) {
                Future<UserDiaryCommentVO> future = userDiaryReplyService.pageByComentId(diaryComment.getId(), userId, 1, 4);
                UserDiaryCommentVO userDiaryComment = future.get(10, TimeUnit.SECONDS);
                userDiaryCommentList.add(userDiaryComment);
                userDiaryComment.setAuthor(diaryComment.getReplyedUserId() == diaryComment.getReplyUserId().longValue() ? true : false);
            }
        }
        Page<UserDiaryCommentVO> pageComment = new Page<>();
        pageComment.setRows(userDiaryCommentList);
        pageComment.setTotal(pageResult.getTotalElements());
        pageComment.setPageNumber(page);
        return pageComment;
    }

    public List<DiaryDO> findByforwardIds(List<Long> forwardIds) {
        return diaryRepository.findAllByForwardIdIn(forwardIds);
    }

    @Autowired
    private DiaryRepository diaryRepository;
    @Autowired
    private UserDiaryZanService userDiaryZanService;
    @Autowired
    private UserDiaryReplyService userDiaryCommentService;
    @Autowired
    private ForwardService forwardService;
    @Autowired
    private UserCenterService userCenterService;
    @Autowired
    private UserDiaryReplyService userDiaryReplyService;
    @Autowired
    private DiaryMessageService diaryMessageService;

    public static final int MINUTE_TIMES = 20;

    public org.springframework.data.domain.Page<DiaryDO> find2DayDiary(Pageable pageable) {
        //获取一小个小时前数据
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(new Date());
        calendar.set(Calendar.MINUTE,
                calendar.get(Calendar.MINUTE) - MINUTE_TIMES);

        Specification<DiaryDO> specification = (root, query, cb) -> {
            List<Predicate> list = new ArrayList<>();
            list.add(cb.greaterThan(root.get("createType"), CreateTypeEnums.DELETE.type));
            list.add(cb.greaterThan(root.get("createTime"), calendar.getTime()));
            return cb.and(list.toArray(new Predicate[list.size()]));
        };
        return diaryRepository.findAll(specification, pageable);
    }

    public List<DiaryDO> findLately1HourDiary(Long userId) {
        Pageable pageable = new PageRequest(0, 100);

        //获取一小个小时前数据
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(new Date());
        calendar.set(Calendar.MINUTE,
                calendar.get(Calendar.MINUTE) - 60);

        Specification<DiaryDO> specification = (root, query, cb) -> {
            List<Predicate> list = new ArrayList<>();
            list.add(cb.equal(root.get("userId"), userId));
            list.add(cb.greaterThan(root.get("createType"), CreateTypeEnums.DELETE.type));
            list.add(cb.greaterThanOrEqualTo(root.get("createTime"), calendar.getTime()));
            return cb.and(list.toArray(new Predicate[list.size()]));
        };
        return diaryRepository.findAll(specification, pageable).getContent();
    }
}
